#!/usr/bin/perl -U
# -*-Perl-*-
#
# 
# Perl filter to handle the log messages from the checkin of files in
# a directory.  This script will group the lists of files by log
# message, and mail a single consolidated log message at the end of
# the commit.
#
# This file assumes a pre-commit checking program that leaves the
# names of the first and last commit directories in a temporary file.
#
# Contributed by David Hampton <hampton@cisco.com>
#
# hacked greatly by Greg A. Woods <woods@web.net>
#
# Copyright (c) Enalean, 2017-2018. All Rights Reserved.
# Heavily modified for the Codendi project at Xerox
# Copyright (c) Xerox Corporation, Codendi / Codendi Team, 2004-2006. All Rights Reserved

# Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [-f logfile]
#	-d		- turn on debugging
#       -G database     - interface to Gnats
#       -nodb           - suppress default codendi database commit tracking
#	-m mailto	- send mail to "mailto" (multiple)
#	-M modulename	- set module name to "modulename"
#	-f logfile	- write commit messages to logfile too
#	-s		- *don't* run "cvs status -v" for each file
#       -T text         - use TEXT in temp file names.
#       -C [reponame]   - Generate viewvc URLS; must be run using %{sVv}
#                         format string.
# (DEPRECATED)      -U URL          - Base URL for viewvc if -C option (above) is used.
#       -D              - generate diffs as part of the notification mail

#
#	Configurable options
#

# Codendi specific
#
# Make sure that log_accum has the s flag and owned by codendiadm.codendiadm
# Like this:
# -rwsrwxr-x  codendiadm codendiadm
#

# Use LWP for Codendi HTTP API
use HTTP::Request::Common qw(POST);
use LWP::UserAgent;
use Encode;

# DB connection
use DBI;
use Text::Iconv;

$utils_path = $ENV{'CODENDI_UTILS_PREFIX'} || "/usr/share/tuleap/src/utils";
require $utils_path."/include.pl";
require $utils_path."/group.pl";
require $utils_path."/cvs1/checkins.pl";
require $utils_path."/hudson.pl";

my $codendi_srv;
if ($sys_https_host) {
  $codendi_srv="https://$sys_https_host";
} else {
  $codendi_srv="http://$sys_default_domain";
}
$codendi_http_srv="http://$sys_default_domain";

# Base name of viewvc specification
$VIEWVC_URL = "$codendi_srv/cvs/viewvc.php/";
$TMPDIR = $cvs_hook_tmp_dir;

# Set this to something that takes "-s".
#$MAILER	       = "/usr/bin/Mail";
# ... or set this to a sendmail clone:
$SENDMAIL = "/usr/sbin/sendmail";

# Used with sprintf to form name of Gnats notification mailing list.
# %s argument comes from -G option.
$GNATS_MAIL_FORMAT = "%s-gnats\@sourceware.cygnus.com";

# Used with sprintf to form name of Gnats root directory.  Potential
# PR info is appended to see if PR actually exists.  %s argument comes
# from -G option.
$GNATS_ROOT_FORMAT = "/sourceware/gnats/%s-db";

# Constants (don't change these!)
#
$STATE_NONE    = 0;
$STATE_CHANGED = 1;
$STATE_ADDED   = 2;
$STATE_REMOVED = 3;
$STATE_LOG     = 4;

# arrays to store all references.
my %references;

my %branches;


# group_id
my $group_id;

#
#	Subroutines
#

sub set_temp_vars {
    local ($name) = @_;

    $LAST_FILE     = sprintf ("$TMPDIR/#%s.lastdir", $name);

    $CHANGED_FILE  = sprintf ("$TMPDIR/#%s.files.changed", $name);
    $ADDED_FILE    = sprintf ("$TMPDIR/#%s.files.added", $name);
    $REMOVED_FILE  = sprintf ("$TMPDIR/#%s.files.removed", $name);
    $LOG_FILE      = sprintf ("$TMPDIR/#%s.files.log", $name);
    $URL_FILE      = sprintf ("$TMPDIR/#%s.files.urls", $name);
    $DB_FILE      = sprintf ("$TMPDIR/#%s.files.DB", $name);

    # Quote for use in a regexp.
    ($FILE_PREFIX   = sprintf ("#%s.files", $name)) =~ s/(\W)/\\$1/g;
}

sub cleanup_tmpfiles {
    local($wd, @files);

    $wd = `pwd`;
    chdir("$TMPDIR") || die("Can't chdir(\"$TMPDIR\")\n");
    opendir(DIR, ".");
    push(@files, grep(/^$FILE_PREFIX\..*\.$id$/, readdir(DIR)));
    closedir(DIR);
    foreach (@files) {
	unlink $_;
    }
    unlink $LAST_FILE . "." . $id;

    chdir($wd);
}

sub write_logfile {
    local($filename, @lines) = @_;

    open(FILE, ">$filename") || die("Cannot open log file >$filename $!.\n");
    print FILE join("\n", @lines), "\n";
    close(FILE);
}

sub format_names {
    local($dir, @files) = @_;
    local(@lines);

    if ($dir =~ /^\.\//) {
	$dir = $';
    }
    if ($dir =~ /\/$/) {
	$dir = $`;
    }
    if ($dir eq "") {
	$dir = ".";
    }

    $format = "\t%-" . sprintf("%d", length($dir) > 15 ? length($dir) : 15) . "s%s ";

    $lines[0] = sprintf($format, $dir, ":");

    if ($debug) {
	print STDERR "format_names(): dir = ", $dir, "; files = ", join(":", @files), ".\n";
    }
    foreach $file (@files) {
	if (length($lines[$#lines]) + length($file) > 65) {
	    $lines[++$#lines] = sprintf($format, " ", " ");
	}
	$lines[$#lines] .= $file . " ";
    }

    @lines;
}

sub format_lists {
    local(@lines) = @_;
    local(@text, @files, $lastdir);

    if ($debug) {
	print STDERR "format_lists(): ", join(":", @lines), "\n";
    }
    @text = ();
    @files = ();
    $lastdir = shift @lines;	# first thing is always a directory
    if ($lastdir !~ /.*\/$/) {
	die("Damn, $lastdir doesn't look like a directory!\n");
    }
    foreach $line (@lines) {
	if ($line =~ /.*\/$/) {
	    push(@text, &format_names($lastdir, @files));
	    $lastdir = $line;
	    @files = ();
	} else {
	    push(@files, $line);
	}
    }
    push(@text, &format_names($lastdir, @files));

    @text;
}

sub accum_subject {
    local(@lines) = @_;
    local(@files, $lastdir);

    $lastdir = shift @lines;	# first thing is always a directory
    @files = ($lastdir);
    if ($lastdir !~ /.*\/$/) {
	die("Damn, $lastdir doesn't look like a directory!\n");
    }
    foreach $line (@lines) {
	if ($line =~ /.*\/$/) {
	    $lastdir = $line;
	    push(@files, $line);
	} else {
	    push(@files, $lastdir . $line);
	}
    }

    @files;
}

sub compile_subject {
    local(@files) = @_;
    local($text, @a, @b, @c, $dir, $topdir);

    # find the highest common directory
    # FIXME: This function is royally dependent on receiving directory
    # entries, such as `my/dir/'.  It would be nice to rewrite it to
    # robustly discover the greatest common directory, but I haven't the time.
    # --gord
    $dir = '-';
    do {
	$topdir = $dir;
	foreach $file (@files) {
	    if ($file =~ /.*\/$/) {
		if ($dir eq '-') {
		    $dir = $file;
		} else {
		    if (index($dir,$file) == 0) {
			$dir = $file;
		    } elsif (index($file,$dir) != 0) {
			@a = split /\//,$file;
			@b = split /\//,$dir;
			@c = ();
			CMP: while ($#a > 0 && $#b > 0) {
			    if ($a[0] eq $b[0]) {
				push(@c, $a[0]);
				shift @a;
				shift @b;
			    } else {
				last CMP;
			    }
			}
			$dir = join('/',@c) . '/';
		    }
		}
	    }
	}
    } until $dir eq $topdir;

    # strip out directories and the common prefix topdir.
    chop $topdir;

    local ($topdir_offset) = length ($topdir);
    $topdir_offset ++ if ($topdir_offset > 0);

    $topdir = '' if ($topdir eq '.');
    @c = ($modulename);
    $c[0] .= '/' . $topdir if ($topdir ne '');
    foreach $file (@files) {
	if (!($file =~ /.*\/$/)) {
	    # Append the filename stripped of the top directory.
	    push(@c, substr($file, $topdir_offset));
	}
    }

    # put it together and limit the length.
    $text = join(' ',@c);
    if (length($text) > 50) {
	$text = substr($text, 0, 47) . '...';
    }

    $text;
}


sub checkin_describe {
  local ($dir) = @_;

  return "$login,$cvsroot,$modulename,$dir";

}

sub db_checkins_add {
  local($index, $commit_id, @descs) = @_;
  @checkins = &read_logfile("$DB_FILE.$index.$id", "");

  if ($debug) {
    print STDERR "treating for $DB_FILE.$index.$id \n";


  }

  if ($sys_cvs_convert_cp1252_to_utf8) {
    $converter = Text::Iconv->new("CP1252", "UTF-8");
    @descs = ($converter->convert("@descs"));
  }

  my ($dir, $i, $who, $repo);
  local $when = localtime(time);
  
  for ($i = 0; $i <= $#checkins; $i ++) {
     if ($checkins[$i] =~ /\/$/)
	    {
	        ($who, $repo, $module, $dir) = split(',', $checkins[$i]);
	    }
	    elsif ($checkins[$i] !~ /\//)
	    {
	      local ($added, $removed);
	      $added = '';
	      $removed = '';

	      $dir =~ s/\/$//;
	      local ($file, $version, $type, $branch) = split(",",$checkins[$i]);
	      if ($debug) {
		print STDERR "add checkin for \n";
		print STDERR " commit_id ", $commit_id, " \n";
		print STDERR " who ", $who, " \n";
		print STDERR " repo ", $repo, " \n";
		print STDERR " module ", $module, " \n";
		print STDERR " when ", $when, " \n";
		print STDERR " dir ", $dir, " \n";
		print STDERR " file ", $file, " \n";
		print STDERR " type ", $type, " \n";
		print STDERR " version ", $version, " \n";
		print STDERR " branch ", $branch, " \n";
		print STDERR " desc ", join("\n", @descs), " \n";
	      }

	      if ($type eq 'c') {
		$added = '999';
		$removed = '999';
	      }

	      &db_add_record($commit_id, $who, $repo, $when, $module."/".$dir, $file, $type, $version, $branch, $added, $removed, @descs);
	    }
   }

}


sub append_names_to_file {
    local($filename, $dir, @files) = @_;

    if (@files) {
	open(FILE, ">>$filename") || die("Cannot open file >>$filename.\n");
	print FILE $dir, "\n";
	print FILE join("\n", @files), "\n";
	close(FILE);
    }
}

sub read_line {
    local($line);
    local($filename) = @_;

    open(FILE, "<$filename") || die("Cannot open file <$filename $!.\n");
    $line = <FILE>;
    close(FILE);
    chop($line);
    $line;
}

sub read_logfile {

    local(@text);
    local($filename, $leader) = @_;

    open(FILE, "<$filename");
    while (<FILE>) {
	chop;
	$line = $leader.$_;
	push(@text, $line);
    }
    close(FILE);

    @text;
}


sub build_header {
    local($header);
    local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime(time);
    local($timezone);

    # Compute timezone
    local($sec,$min,$hourGM,$mday,$mon,$yearGM,$wday,$ydayGM) = gmtime(time);

    # New Year's Eve..
    if ($yearGM<$year) {$hourGM-=24;}
    elsif ($yearGM>$year) {$hourGM+=24;}
    elsif ($ydayGM>$yday) {$hourGM+=24;}
    elsif ($ydayGM<$yday) {$hourGM-=24;}

    if ($hourGM>$hour) {
      $timezone="GMT-".($hourGM-$hour);
    } else {
      $timezone="GMT+".($hour-$hourGM);
    }

    $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\n",
		      $cvsroot,
		      $modulename);
    if (defined($branch)) {
	$header .= sprintf("Branch: \t%s\n",
		      $branch);
    }
    $header .= sprintf("Changes by:\t%s\t%02d/%02d/%02d %02d:%02d:%02d %s",
		      "$fullname <$mailname>",
		      $year%100, $mon+1, $mday,
		      $hour, $min, $sec, $timezone);
}

sub mail_notification {
    local($names, $subject, @text) = @_;

    print STDERR "mail_notification: to $names, subject $subject\n" if ($debug);
    $subject = encode("MIME-Header", decode("UTF-8", $subject));
    if ($SENDMAIL ne '')
    {
	my ($comma_names) = join (', ', split (/\s+/, $names));
        # Encode fullname
        $encfullname = encode('MIME-Q', $fullname);
	unshift (@text,
		 "To: $comma_names",
		 "Subject: $subject",
		 "Reply-to: $mailname",
		 "Content-type: text/plain; charset=utf-8",
		 ''); # We need this blank line before the message body.
	open (MAIL, "| $SENDMAIL -bm -F\"$encfullname\" $names");
    }
    else
    {
	open(MAIL, "| $MAILER -s \"$subject\" $names");
    }
    print MAIL join("\n", @text), "\n";
    close(MAIL);
}

sub write_commitlog {
    local($logfile, @text) = @_;

    open(FILE, ">>$logfile");
    print FILE join("\n", @text), "\n\n";
    close(FILE);
}

# Return a list of all ViewVC URLs for this commit.
sub generate_viewvc_urls {
    local ($dir, $branch, @files) = @_;
    local (@sp, @result);
    local ($start) = $VIEWVC_URL;
    local ($args) = '?';
    local ($need_amp) = 0;

# This next if assumes that if a repository name is not given with the
# -C option, we then have to append the project name to the VIEWVC_URL
# and the project name has been defined by the -T option
# (this works for the particular setup in savannah.gnu.org and Codendi
# but will have to be changed in other sites.)
    if ($viewvc_name eq '')
    {
	$start .= $temp_name . '/';
    }
    else
    {
	$args .= 'root=' . $viewvc_name;
	$need_amp = 1;
    }
    $start .= $dir . '/';
    if ($branch ne '') {
	$args .= '&' if ($need_amp);
	$args .= 'pathrev=' . $branch;
	$need_amp = 1;
    }

    # Add an ampersand if we need one.
    $args .= '&' if ($need_amp);
    $need_amp = 0;

    local ($r1,$r2);
    foreach (@files) {
	# List is (FILE OLD-REV NEW-REV).
	@sp = split (',');
	$r1 = $sp[1];
	$r2 = $sp[2];
	if ($r1 eq 'NONE')
        # If it's a new file, it is listed completely
	{
	    push (@result, ($start . $sp[0] . $args. 'revision=' . $r2 . '&view=markup'));
	}
        # If it's a file update, differences are shown. A value of NONE
        # for NEW-REV indicates a file removed and no URL is generated.
	elsif ($r2 ne 'NONE')
	{
	    push (@result, ($start . $sp[0] . $args. 'r1=' . $r1
			 . '&r2=' . $r2 . '&roottype=cvs'));
	}
    }
    return @result;
}


sub get_branch {
  local ($file) = @_;
  if ($debug) {
    print STDERR "entering get_branch\n";
  }

  local (@files, @keys, $key, $f);
  @keys = keys %branches;
  foreach $key (@keys) {
    $files = $branches{$key};
    if ($debug) {
      print STDERR $key, " branch value ", $files, " inspection for ", $file, "\n";
    }
    @filelist = split(' ', $files);
    foreach $f (@filelist) {
      if ($debug) {
	print STDERR $key, " branch inspection for ", $file, " and file ", $f, "\n";
      }

      if ($file eq $f) {
	if ($debug) {
	  print STDERR "branch found: ", $key, "\n";
	}
	return $key;
      }
    }
  }
  if ($debug) {
    print STDERR "branch not found\n";
  }
  return 'not found';
}


# Return a list of all viewvc URLs for this commit.
sub generate_checkins {
    local ($dir, @files) = @_;
    local (@sp, @result);

    if ($debug) {
      print STDERR "entering generate_checkins with ", $dir, ": ", join(' ', @files), "\n";
    }

    local ($rev,$file);
    foreach (@files) {
      if ($debug) {
	print STDERR $_, " file in generate_checkins \n";
      }

      # List is (FILE OLD-REV NEW-REV).
      @sp = split (',');
      $file = $sp[0];
      $rev = $sp[2];
      if ($debug) {
	print STDERR "looking for ",$file, $rev, " branch \n";
      }

      # then, go and find the branch if any for file
      local ($branch) = &get_branch($file);
      local (@res) = ($file, $rev, $branch);
      push (@result, join(',', @res));
    }
    if ($debug) {
      print STDERR " checkin : ", join(',', @res);
    }
    return @result;
}


# Codendi - extract all items that needs to be cross-referenced
# in the log message
sub extract_xrefs {
    my (@log) = @_;
    if ($sys_cvs_convert_cp1252_to_utf8) {
      $converter = Text::Iconv->new("CP1252", "UTF-8");
      @log = ($converter->convert("@log"));
    }
    # Use Codendi HTTP API
    my $ua = LWP::UserAgent->new;
    $ua->agent('Codendi Perl Agent');

    if (!$group_id) {
      $group_id=100;
    }
    $text=join("\n",@log);
    $cvstype="cvs_commit";
    # HTTPS is not supported by LWP on RHEL3 -> use HTTP
    my $req = POST "$codendi_http_srv/api/reference/extractCross.php",
      [ group_id => "$group_id", text => "$text", rev_id=>"$commit_id", login=>"$login", type=>"$cvstype" ];
   
    my $response = $ua->request($req);
    if ($response->is_success) {
      my $desc="";
      my $match="";
      my $link="";
      foreach (split(/\n/,$response->content)) {
        chomp;
        if (! $_) {;} # skip empty lines
        elsif (!$desc) {$desc=$_;}
        elsif (!$match) {$match=$_;}
        else {
          $link=$_;
          $references{"$desc"}{"$match"}=$link;
          print STDERR "Found match: $match\n" if $debug;
          $desc=$match=$link=0;
        }
      }
    }
    else {
      warn $response->status_line;
    }
}

#
#	Main Body
#

# Initialize basic variables
#
$debug = 0;
$id = getpgrp();		# note, you *must* use a shell which does setpgrp()
$state = $STATE_NONE;
local ($login, $gecos);
$login = $ENV{'USER'};
if (! $login)
{
    ($login, $gecos) = (getpwuid ($<))[0,6];
}
else
{
    $login = "nobody" if (! $login);
    $gecos = (getpwnam ($login))[6];
}

# Determine the mailname and fullname.
if ($gecos =~ /^([^<]*\s+)<(\S+@\S+)>/)
{
    $fullname = $1;
    $mailname = $2;
    $fullname =~ s/\s+$//;
}
else
{
    $fullname = $gecos;
    $fullname =~ s/,.*$//;

    local ($hostdomain, $hostname);
    chop($hostdomain = `hostname -f`);
    if ($hostdomain !~ /\./)
    {
        chop($hostname = `hostname`);
        if ($hostname !~ /\./) {
            chop($domainname = `domainname`);
            $hostdomain = $hostname . "." . $domainname;
        } else {
            $hostdomain = $hostname;
        }
    }
    $mailname = "$login\@$hostdomain";
}
$cvsroot = $ENV{'CVSROOT'};
$do_status = 1;
$modulename = "";
$temp_name = "temp";
$do_viewvc = 0;
$viewvc_name = '';
$do_diffs = 0;
$codendidb = 1;
%diffmon = ();

# parse command line arguments (file list is seen as one arg)
#
if ($debug) {
    print STDERR "Log accum ARGV:\n";
    print STDERR @ARGV;
    print STDERR "\n---\n";
}

while (@ARGV) {
    $arg = shift @ARGV;

    if ($arg eq '-d') {
	$debug = 1;
	print STDERR "Debug turned on...\n";
    } elsif ($arg eq '-m') {
	$mailto = "$mailto " . shift @ARGV;
    } elsif ($arg eq '-M') {
	$modulename = shift @ARGV;
    } elsif ($arg eq '-s') {
	$do_status = 0;
    } elsif ($arg eq '-f') {
	($commitlog) && die("Too many '-f' args\n");
	$commitlog = shift @ARGV;
    } elsif ($arg eq '-G') {
	($gnatsdb) && die("Too many '-G' args\n");
	$gnatsdb = shift @ARGV;
    } elsif ($arg eq '-nodb') {
	$codendidb = 0;
    } elsif ($arg eq '-T') {
	$temp_name = shift @ARGV;
    } elsif ($arg eq '-C') {
	$do_viewvc = 1;
	$viewvc_name = shift @ARGV
	    if ($#ARGV >= 0 && $ARGV[0] !~ /^-/);
    } elsif ($arg eq '-U') { 
	my $OLD_VIEWVC_URL = shift @ARGV; #DEPRACATED: URL is now computed
    } elsif ($arg eq '-D') {
	$do_diffs = 2;
    } elsif ($arg =~ /^-D=(.*)$/) {
	$do_diffs = 2;
	$diffmailto = $1;
    } elsif ($arg =~ /^-D([^=]+)$/) {
	$do_diffs = 1 if (! $do_diffs);
	$diffmon{$1} = 'DEFAULT';
    } elsif ($arg =~ /^-D([^=]+)=(.*)$/) {
	$do_diffs = 1 if (! $do_diffs);
	$diffmon{$1} = $2;
    } else {
	($donefiles) && die("Too many arguments!  Check usage.", $arg, "\n");
	$donefiles = 1;
        # @files = split(/ /, $arg);
	# There is a problem with filenames containing a space.
        # example of input: 'module1/module1 test 2.zip,1.8,1.9 test3.zip,1.8,1.9 test 4 or 5.zip,1.8,1.9'
        # we can't just split on blank chars, so we'll rearrange the list like this:
	# module1/module1
	# test 2.zip,1.8,1.9
	# test3.zip,1.8,1.9
	# test 4 ou 5.zip,1.8,1.9
        ($mod,$filelist) = split(/ /, $arg,2);
         $arglist="$mod\n";
         $line=0;
        foreach $f (split(/,/,$filelist)) {
            if ($line && (($line+1)%2) ) {
                ($v,$f2)=split(/ /,$f,2);
                if ($f2) {$arglist.="$v\n$f2,";}
                else {$arglist.="$v";}
            } else {
                $arglist.=$f.",";
            }
            $line++;
        }
        chomp($arglist);

	@files = split(/\n/, $arglist);
	if ($debug) {
	  foreach $f (@files) {
	    print STDERR $f, ", ";
	  }
	}
    }
}

if ($debug) {
  print STDERR "\nentering log_accum\n";
}

($mailto) || ($codendidb) || die("No -m mail recipient specified\n");
$mailto =~ s/^\s+//;
&set_temp_vars ($temp_name);

# for now, the first "file" is the repository directory being committed,
# relative to the $CVSROOT location
#
@path = split('/', $files[0]);

# XXX there are some ugly assumptions in here about module names and
# XXX directories relative to the $CVSROOT location -- really should
# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
# XXX we have to parse it backwards.
#
if ($modulename eq "") {
    $modulename = $path[0];	# I.e. the module name == top-level dir
}
if ($commitlog ne "") {
    $commitlog = $cvsroot . "/" . $modulename . "/" . $commitlog unless ($commitlog =~ /^\//);
}
if ($#path == 0) {
    $dir = ".";
} else {
    $dir = join('/', @path[1..$#path]);
}
$dir = $dir . "/";

if ($debug) {
    print STDERR "module - ", $modulename, "\n";
    print STDERR "dir    - ", $dir, "\n";
    print STDERR "path   - ", join(":", @path), "\n";
    print STDERR "files  - ", join(":", @files), "\n";
    print STDERR "id     - ", $id, "\n";
}


if ($codendidb) {

  &db_connect;

  # retrieve the group_id
  ($rootnull, $root, $gname) = split ('/', $cvsroot);
  $group_id = &set_group_info_from_name($gname);
  if ($debug) {
    print STDERR "group_repo: ", $gname, "\n";
    print STDERR "group_id: ", $group_id, "\n";
  }

  $cvsmailto = &cvsGroup_mailto();

  $cvsmailheader = &cvsGroup_mail_header();

  if ($debug) {
    print STDERR "\n header in db: ", $cvsmailheader, "\n";
  }
  if ($cvsmailheader eq 'NULL') {
    ##$cvsmailheader = "[cvs-".$gname."]";
    $cvsmailheader = "";
  }

  if ($debug) {
    print STDERR "mailto: ", $mailto, "\n";
    print STDERR "cvsmailto: ", $cvsmailto, "\n";
  }

  if ($cvsmailto ne 'NULL') {
    if ($debug) {
      print STDERR "update mailto with database controled mail\n";
    }
    if ($mailto) {
      $mailto = "$mailto , $cvsmailto";
    } else {
      $mailto = $cvsmailto;
    }
  }
  if ($debug) {
    print STDERR "mailto: ", $mailto, "\n";
  }
}


# Check for a new directory first.  This appears with files set as follows:
#
#    files[0] - "path/name/newdir"
#    files[1] - "-"
#    files[2] - "New"
#    files[3] - "directory"
#
if ($files[2] =~ /New/ && $files[3] =~ /directory/) {
    local(@text);

    @text = ();
    push(@text, &build_header());
    push(@text, "");
    push(@text, $files[0]);
    push(@text, "");

    while (<STDIN>) {
	chop;			# Drop the newline
	push(@text, $_);
    }

    ## track in cvs database
    $db_track = 0;
    if ($codendidb) {
      $db_track = &isGroupCvsTracked();
      if ($debug) {
	print STDERR "tracking: ", $db_track, "\n";
      }
    }
    if ($db_track) {
      $commit_id = db_get_commit($debug);
      if ($debug) {
	
	print STDERR "desc: ", join ('\n', @text), "\n";
      }
      @descs = split("\n", "\tDirectory\n\n".$files[0]."\n\n\tadded to repository.");
      
      &db_add_record($commit_id, $ENV{'USER'}, $cvsroot, '', $files[0], '.', 'a', '', '', 'NULL', 'NULL', @descs);
    }

    if ($mailto) {
      &mail_notification($mailto, $cvsmailheader.$files[0], @text);
    }

    if ($commitlog) {
	&write_commitlog($commitlog, @text);
    }


    exit 0;
}

# Iterate over the body of the message collecting information.
#



if ($debug) {
  print STDERR $codendidb, " value for codendidb \n";
}

while (<STDIN>) {
    if ($debug) {
      print STDERR $_, "\n";
    }
    chop;			# Drop the newline

    if (/^Modified Files/) { $state = $STATE_CHANGED; next; }
    if (/^Added Files/)    { $state = $STATE_ADDED;   next; }
    if (/^Removed Files/)  { $state = $STATE_REMOVED; next; }
    if (/^Log Message/)    { $state = $STATE_LOG;     next; }
    if (/^\s*Tag:|Revision\/Branch/) { /^[^:]+:\s*(.*)/; $branch = $+; next; }

    s/^[ \t\n]+//;		# delete leading whitespace
    s/[ \t\n]+$//;		# delete trailing whitespace

    if ($state == $STATE_CHANGED) {
      push(@changed_files, split);

      if ($branches{'c,'.$branch}) {
	$branched = $branches{'c,'.$branch}.' '.$_;
	$branches{'c,'.$branch} = $branched;
      } else {
	$branches{'c,'.$branch} =  $_;
      }
    }

    
    if ($state == $STATE_ADDED)   {
      push(@added_files,   split);
      if ($branches{'a,'.$branch}) {
	$branched = $branches{'a,'.$branch}.' '.$_;
	$branches{'a,'.$branch} = $branched;
      } else {
	$branches{'a,'.$branch} =  $_;
      }
    }

    if ($state == $STATE_REMOVED) { 
      push(@removed_files, split);
      if ($branches{'r,'.$branch}) {
	$branched = $branches{'c,'.$branch}.' '.$_;
	$branches{'r,'.$branch} = $branched;
      } else {
	$branches{'r,'.$branch} =  $_;
      }
    }
    if ($state == $STATE_LOG)     { push(@log_lines,     $_); }
  }


# Strip leading and trailing blank lines from the log message.  Also
# compress multiple blank lines in the body of the message down to a
# single blank line.
#
while ($#log_lines > -1) {
    last if ($log_lines[0] ne "");
    shift(@log_lines);
}
while ($#log_lines > -1) {
    last if ($log_lines[$#log_lines] ne "");
    pop(@log_lines);
}
for ($i = $#log_lines; $i > 0; $i--) {
    if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) {
	splice(@log_lines, $i, 1);
    }
}

# Check for an import command.  This appears with files set as follows:
#
#    files[0] - "path/name"
#    files[1] - "-"
#    files[2] - "Imported"
#    files[3] - "sources"
#
if ($files[2] =~ /Imported/ && $files[3] =~ /sources/) {
    local(@text);

    @text = ();
    push(@text, &build_header());
    push(@text, "");

    push(@text, "Log message:");
    while ($#log_lines > -1) {
	push (@text, "    " . $log_lines[0]);
	shift(@log_lines);
    }

    if ($mailto) {
      &mail_notification($mailto,  $cvsmailheader." Import $file[0]", @text);
    }

    if ($commitlog) {
	&write_commitlog($commitlog, @text);
    }
    
    $db_track = 0;
    $db_events_list = 'NULL';
    if ($codendidb) {
      $db_track = &isGroupCvsTracked();
      $db_events_list = &cvsGroup_mailto();
      if ($debug) {
	print STDERR "tracking: ", $db_track, "\n";
	print STDERR "mailing: ", $db_events_list, "\n";
      }
      $codendidb = $codendidb && (($db_events_list != 'NULL') || $db_track);
    }
    if ($db_track) {
      $commit_id = db_get_commit($debug);
      if ($debug) {
	
	print STDERR "desc: ", join ('\n', @text), "\n";
      }
      &db_add_record($commit_id, $ENV{'USER'}, $cvsroot, '', $modulename, '.', 'a', '', '', 'NULL', 'NULL', @text);
    }

    exit 0;
}

# Compute the list of viewvc URLs if necessary.
if ($do_viewvc) {
    @urls = &generate_viewvc_urls (join ('/', @path), $branch,
				   @files[1 .. $#files]);
}

if ($codendidb) {
  $db_track = &isGroupCvsTracked();
  $db_events_list = &cvsGroup_mailto();
  if ($debug) {
     print STDERR "group_id: ", $group_id, "\n";
    print STDERR "tracking: ", $db_track, "\n";
    print STDERR "mailing: ", $db_events_list, "\n";
  }
  $codendidb = $codendidb && (($db_events_list != 'NULL') || $db_track);
}
if ($codendidb) {  
  if ($debug) {
    local @keys = keys %branches;
    print STDERR $#keys , " found branches\n";
    print STDERR "calling for checkins with ",
      join ('/', @path),
	" with ",
	  join ('|', @keys),
	    " branches and ",
	      join (' ', @files[1 .. $#files]),
		" files\n";
  }
  @checkins = &generate_checkins (join ('/', @path),
				   @files[1 .. $#files]);
}

if ($debug) {
    print STDERR "Searching for log file index...";
}
# Find an index to a log file that matches this log message
#
for ($i = 0; ; $i++) {
    local(@text);

    last if (! -e "$LOG_FILE.$i.$id"); # the next available one
    @text = &read_logfile("$LOG_FILE.$i.$id", "");
    last if ($#text == -1);	# nothing in this file, use it
    last if (join(" ", @log_lines) eq join(" ", @text)); # it's the same log message as another
}
if ($debug) {
    print STDERR " found log file at $i.$id, now writing tmp files.\n";
}

# Spit out the information gathered in this pass.
#
&append_names_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files);
&append_names_to_file("$ADDED_FILE.$i.$id",   $dir, @added_files);
&append_names_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files);
&append_names_to_file("$URL_FILE.$i.$id",     $dir, @urls);
if ($codendidb) {
  &append_names_to_file("$DB_FILE.$i.$id",    &checkin_describe($dir), @checkins);
}
&write_logfile("$LOG_FILE.$i.$id", @log_lines);

# Check whether this is the last directory.  If not, quit.
#
if ($debug) {
    print STDERR "Checking current dir against last dir.\n";
}
$_ = &read_line("$LAST_FILE.$id");

if ($_ ne $cvsroot . "/" . $files[0]) {
    if ($debug) {
	print STDERR sprintf("Current directory %s is not last directory %s.\n", $cvsroot . "/" .$files[0], $_);
    }
    exit 0;
}
if ($debug) {
    print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $files[0], $_);
}

#
#	End Of Commits!
#

# This is it.  The commits are all finished.  Lump everything together
# into a single message, fire a copy off to the mailing list, and drop
# it on the end of the Changes file.
#

#
# Produce the final compilation of the log messages
#
@text = ();
@status_txt = ();
@diff_txt = ();
@subject_files = ();
@log_txt = ();
push(@text, &build_header());
push(@text, "");

local ($commit_id);

if ($db_track) {
  $commit_id = db_get_commit($debug);
}



for ($i = 0; ; $i++) {
    last if (! -e "$LOG_FILE.$i.$id"); # we're done them all!
    @lines = &read_logfile("$CHANGED_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, "Modified files:");
	push(@text, &format_lists(@lines));
	push(@subject_files, &accum_subject(@lines));
    }
    @lines = &read_logfile("$ADDED_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, "Added files:");
	push(@text, &format_lists(@lines));
	push(@subject_files, &accum_subject(@lines));
    }
    @lines = &read_logfile("$REMOVED_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, "Removed files:");
	push(@text, &format_lists(@lines));
	push(@subject_files, &accum_subject(@lines));
    }
    if ($#text >= 0) {
	push(@text, "");
    }
    @log_txt = &read_logfile("$LOG_FILE.$i.$id", "\t");
    if ($#log_txt >= 0) {
	push(@text, "Log message:");
	push(@text, @log_txt);
	push(@text, "");
    }
    if ($db_track) {
        # Codendi: extract references from log message
        &extract_xrefs(@log_txt)
        &db_checkins_add($i, $commit_id, @log_txt);
    }
    @url_txt = &read_logfile("$URL_FILE.$i.$id", "");
    if ($#url_txt >= 0) {
	push (@text, "Pointers to file changes:");
	# Exclude directories listed in the file.
	push (@text, grep (! /\/$/, @url_txt));
	push (@text, "");
    }

    my $desc;
    my $match;
    my $initialized='';
    foreach $desc (sort(keys(%references))) {
      if (!$initialized) {
        push (@text, "");
        push (@text, "References:");
        $initialized=1;
      }

      push (@text, "");
      push (@text, "$desc:");
      foreach $match (sort(keys %{$references{"$desc"}})) {
        push (@text," $match: ".$references{"$desc"}{"$match"});
        print STDERR "Match: ".$references{"$desc"}{"$match"} if $debug;
      }
    }

    if ($do_status || $do_diffs) {
	local(@changed_files);

	@changed_files = ();
	push(@changed_files, &read_logfile("$CHANGED_FILE.$i.$id", ""));
	push(@changed_files, &read_logfile("$ADDED_FILE.$i.$id", ""));
	push(@changed_files, &read_logfile("$REMOVED_FILE.$i.$id", ""));

	if ($debug) {
	    print STDERR "main: pre-sort changed_files = ", join(":", @changed_files), ".\n";
	}

	# Add the directory to each changed_file.
	my ($lastdir, $i);
	for ($i = 0; $i <= $#changed_files; $i ++)
	{
	    if ($changed_files[$i] =~ /\/$/)
	    {
		$lastdir = $changed_files[$i];
	    }
	    elsif ($lastdir ne './' && $changed_files[$i] !~ /\//)
	    {
		$changed_files[$i] = $lastdir . $changed_files[$i];
	    }
	}

	@changed_files = sort(@changed_files);
	if ($debug) {
	    print STDERR "main: post-sort changed_files = ", join(":", @changed_files), ".\n";
	}

	foreach $dofile (@changed_files) {
	    if ($dofile =~ /\/$/) {
		next;		# ignore the silly "dir" entries
	    }
	    if ($do_diffs > 1 || $diffmon{$dofile})
	    {
		my $addr = 'DEFAULT';
		$addr = $diffmailto if ($diffmailto);
		$addr = $diffmon{$dofile} if ($diffmon{$dofile});
		my (@diff) = ();
		if ($debug) {
		    print STDERR "main(): doing diff on $dofile\n";
		}
		open (DIFF, "-|")
		    || exec 'cvs', '-nQq', 'rdiff', '-tc', "$modulename/$dofile";
		while (<DIFF>) {
		    # FIXME: Rearrange the diffs so that they make valid
		    # `patch' input.
		    chop;
		    push (@diff, $_);
		}

		if ($addr eq 'DEFAULT')
		{
		    push (@diff_txt, @diff);
		}
		else
		{
		    &mail_notification ($addr,
					 $cvsmailheader." Changes to $modulename/$dofile",
					@diff);
		}
	    }
	    if ($do_status)
	    {
		if ($debug) {
		    print STDERR "main(): doing status on $dofile\n";
		}
		open(STATUS, "-|")
		    || exec 'cvs', '-nQq', 'status', '-v', $dofile;
		while (<STATUS>) {
		    chop;
		    push(@status_txt, $_);
		}
	    }
	}
    }
}

if ($debug) {
  print STDERR "mailto: ", $mailto, "\n";
}
if ($mailto) {
  
  $subject_txt = &compile_subject(@subject_files);

  # Write to the commitlog file
  #
  if ($commitlog) {
    &write_commitlog($commitlog, @text);
  }

  if ($#diff_txt >= 0) {
    push (@text, "Patches:");
    push (@text, @diff_txt);
  }

  if ($#status_txt >= 0) {
    push(@text, @status_txt);
  }

  # Mailout the notification.
  #
  &mail_notification($mailto, $cvsmailheader.$subject_txt, @text);
}

# Send mail to Gnats, if required.
if ($gnatsdb ne '') {
    $log_txt = join ("\n", @log_txt);
    if ($log_txt =~ m,PR ([a-z.]+/[0-9]+),) {
	$pr = $1;
	$file = sprintf ($GNATS_ROOT_FORMAT, $gnatsdb) . "/" . $pr;
	if (-f $file) {
	    &mail_notification(sprintf ($GNATS_MAIL_FORMAT, $gnatsdb),
			       $pr, @text);
	}
    }
}

# Trigger Continuous Integration build if needed.
if ($codendidb) {
    &trigger_hudson_builds($group_id, 'cvs');
}

# cleanup
#
if (! $debug) {
    &cleanup_tmpfiles();
}

exit 0;
